package com.satan.novel.config.aspect;

import com.satan.novel.config.annotation.Key;
import com.satan.novel.config.annotation.Lock;
import com.satan.novel.config.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.concurrent.TimeUnit;

/**
 * @Author: Demon
 * @Date: 2024/1/31 11:25
 * @Description:
 **/
// 定义一个基于AOP（面向切面编程）的组件，用于处理分布式锁逻辑
@Aspect
@Component
@RequiredArgsConstructor // 通过构造器注入依赖项，确保RedissonClient实例被注入
public class LockAspect {

    // 全局锁前缀常量
    private static final String KEY_PREFIX = "Lock";
    // 锁key分隔符常量
    private static final String KEY_SEPARATOR = "::";
    // 注入Redisson客户端，用于操作Redis分布式锁
    private final RedissonClient redissonClient;

    // 切点表达式，该方法会在标记有`@Lock`注解的方法执行前后进行拦截
    @Around(value = "@annotation(com.satan.novel.config.annotation.Lock)")
    @SneakyThrows
    public Object doAround(ProceedingJoinPoint joinPoint) {
        // 获取当前执行方法的信息
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method targetMethod = methodSignature.getMethod();

        // 获取目标方法上的`@Lock`注解
        Lock lock = targetMethod.getAnnotation(Lock.class);

        // 构建分布式锁的key，根据注解和方法参数动态生成
        String lockKey = KEY_PREFIX + buildLockKey(lock.prefix(), targetMethod, joinPoint.getArgs());

        // 创建并获取Redisson分布式锁对象
        RLock rLock = redissonClient.getLock(lockKey);

        // 尝试获取锁，如果需要等待则设置超时时间
        if (lock.isWait() ? rLock.tryLock(lock.waitTime(), TimeUnit.SECONDS) : rLock.tryLock()) {
            try {
                // 获取锁成功后，执行原方法
                return joinPoint.proceed();
            } finally {
                // 方法执行完后，释放锁
                rLock.unlock();
            }
        }

        // 如果未能获取到锁，则抛出业务异常
        throw new BusinessException(lock.failCode());
    }

    // 根据指定的前缀、方法及方法参数构建分布式锁的key
    private String buildLockKey(String prefix, Method method, Object[] args) {
        StringBuilder builder = new StringBuilder(KEY_PREFIX);

        // 添加自定义前缀（如果存在）
        if (StringUtils.hasText(prefix)) {
            builder.append(KEY_SEPARATOR).append(prefix);
        }

        // 遍历方法的所有参数，找到标记有`@Key`注解的参数，将其值添加至锁key中
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            if (parameters[i].isAnnotationPresent(Key.class)) {
                Key key = parameters[i].getAnnotation(Key.class);
                builder.append(KEY_SEPARATOR).append(parseKeyExpr(key.expr(), args[i]));
            }
        }

        return builder.toString();
    }

    // 解析并计算给定表达式的值，将结果作为锁key的一部分
    private String parseKeyExpr(String expr, Object arg) {
        if (!StringUtils.hasText(expr)) {
            // 表达式为空时直接返回参数值的字符串形式
            return arg.toString();
        }

        // 创建一个SpEL（Spring Expression Language）表达式解析器实例
        ExpressionParser parser = new SpelExpressionParser();

        // 使用解析器根据给定的expr字符串和TemplateParserContext上下文对象解析并构建一个SpEL表达式对象
        // 这里expr是一个SpEL表达式，它可以包含方法调用、属性访问等复杂逻辑
        // TemplateParserContext用于支持模板表达式解析，比如#{...}形式的占位符
        Expression expression = parser.parseExpression(expr, new TemplateParserContext());

        // 使用SpEL解析表达式，并将结果转换为String类型
        return expression.getValue(arg, String.class);
    }
}

